﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;

namespace Location2XML
{
	enum XMLElement
	{
		LocationList,
		Location,
		Country,
		State,
		Name,
	}

	enum XMLAttribute
	{
		Latitude,
		Longitude,
		Country,
		State,
		ID,
	}

	internal static class ReaderExtensions
	{
		internal static void ForEachData(this BinaryReader reader, Action<int> body)
		{
			int count = reader.ReadInt32();
			for (int i = 0; i < count; i++)
				body(i);
		}

		internal static Country ReadCountry(this BinaryReader reader, ICollection<Country> countries)
		{
			string name = reader.ReadString();
			var country = countries.FirstOrDefault(c => c.Name == name);
			if (country == null)
			{
				country = new Country(name);
				countries.Add(country);
			}
			return country;
		}

		internal static State ReadState(this BinaryReader reader, Country country)
		{
			string name = reader.ReadString();
			if (String.IsNullOrEmpty(name)) return null;
			var state = country.States.FirstOrDefault(s => s.Name == name);
			if (state == null)
			{
				state = new State(name);
				country.States.Add(state);
			}
			return state;
		}
	}

	internal static class WriterExtensions
	{
		const string NS = "urn:denebsoft-skymap-location";

		internal static void WriteDocumentElement(this XmlWriter writer, XMLElement name, Action body)
		{
			writer.WriteStartDocument();
			writer.WriteStartElement(name.ToString(), NS);
			body();
			writer.WriteEndElement();
			writer.WriteEndDocument();
		}

		internal static void WriteElement(this XmlWriter writer, XMLElement name, Action body)
		{
			writer.WriteStartElement(name.ToString(), NS);
			body();
			writer.WriteEndElement();
		}

		internal static void WriteElement(this XmlWriter writer, XMLElement name, string value, Action body)
		{
			if (value == " ") return;
			writer.WriteStartElement(name.ToString(), NS);
			body();
			writer.WriteString(value);
			writer.WriteEndElement();
		}

		internal static void WriteAttribute(this XmlWriter writer, XMLAttribute name, double value)
		{
			writer.WriteAttributeString(name.ToString(), XmlConvert.ToString(value));
		}

		internal static void WriteAttribute(this XmlWriter writer, XMLAttribute name, string value)
		{
			if (!String.IsNullOrEmpty(value))
				writer.WriteAttributeString(name.ToString(), value);
		}

		internal static void WriteAttributeLanguage(this XmlWriter writer, string lang)
		{
			writer.WriteAttributeString("xml", "lang", null, lang);
		}
	}

	public class Country
	{
		public string Name { get; set; }
		public string ID { get; private set; }
		public ICollection<State> States { get; private set; }

		public Country(string id, string name)
		{
			this.Name = name;
			this.ID = id;
			this.States = new List<State>();
		}

		public Country(string name)
		{
			this.Name = name;
			this.ID = name.Replace(" ", "");
			this.States = new List<State>();
		}
	}

	public class State
	{
		public string Name { get; set; }
		public string ID { get; private set; }

		public State(string id, string name)
		{
			this.Name = name;
			this.ID = id;
		}

		public State(string name)
		{
			this.Name = name;
			this.ID = name.Replace(" ", "");
		}
	}

	class Program
	{
		static void Main(string[] args)
		{
			string ifn = args.Length == 0 || Path.GetExtension(args[0]) == ".xml" ? "location.dat" : args[0];
			string ofn = args.Length == 1 && Path.GetExtension(args[0]) == ".xml" ? args[0] : args.Length <= 1 ? "location.xml" : args[1];

			if (args.Length > 2 || Path.GetExtension(ifn) != ".dat" || Path.GetExtension(ofn) != ".xml" ||
				args.Length > 1 && Path.GetExtension(args[0]) == ".xml")
			{
				Console.WriteLine("usage: {0} [location.dat] [location.xml]", Path.GetFileNameWithoutExtension(System.Reflection.Assembly.GetExecutingAssembly().Location));
				return;
			}

			using (var ifs = new FileStream(ifn, FileMode.Open))
			using (var reader = new BinaryReader(ifs))
			using (var writer = XmlWriter.Create(ofn, new XmlWriterSettings()
			{
				Encoding = Encoding.UTF8,
				Indent = true,
				IndentChars = "\t",
				OmitXmlDeclaration = false
			}))
			{
				writer.WriteDocumentElement(XMLElement.LocationList, () =>
				{
					var countries = new List<Country>();

					reader.ForEachData(i =>
					{
						writer.WriteElement(XMLElement.Location, () =>
						{
							string Name = reader.ReadString();
							var Country = reader.ReadCountry(countries);
							var State = reader.ReadState(Country);
							double Latitude = reader.ReadDouble();
							double Longitude = reader.ReadDouble();
							writer.WriteAttribute(XMLAttribute.Latitude, Latitude);
							writer.WriteAttribute(XMLAttribute.Longitude, Longitude);
							writer.WriteAttribute(XMLAttribute.Country, Country.ID);
							if (State != null)
								writer.WriteAttribute(XMLAttribute.State, State.ID);

							writer.WriteElement(XMLElement.Name, Name, () =>
							{
								writer.WriteAttributeLanguage("en");
							});
						});
					});

					foreach (var country in countries)
					{
						writer.WriteElement(XMLElement.Country, () =>
						{
							writer.WriteAttribute(XMLAttribute.ID, country.ID);
							writer.WriteElement(XMLElement.Name, country.Name, () =>
							{
								writer.WriteAttributeLanguage("en");
							});

							foreach (var state in country.States)
							{
								writer.WriteElement(XMLElement.State, () =>
								{
									writer.WriteAttribute(XMLAttribute.ID, state.ID);
									writer.WriteElement(XMLElement.Name, state.Name, () =>
									{
										writer.WriteAttributeLanguage("en");
									});
								});
							}
						});
					}
				});
			}
		}
	}
}
